from nox.lib.core import * from nox.lib.packet.ethernet import ethernet from nox.lib.packet.arp import arp from nox.lib.packet.ipv4 import ipv4 from nox.lib.packet.tcp import tcp from nox.lib.packet.packet_utils import mac_to_str, mac_to_int, ip_to_str from twisted.python import log import logging from time import time from socket import htons from struct import unpack logger = logging.getLogger('nox.coreapps.examples.myswitch') # Global myswitch instance inst = None # Timeout for cached MAC entries CACHE_TIMEOUT = 5 # -- # Given a packet, learn the source and peg to a switch/inport # -- def do_l2_learning(dpid, inport, packet): global inst # learn MAC on incoming port srcaddr = packet.src.tostring() if ord(srcaddr[0]) & 1: return if inst.st[dpid].has_key(srcaddr): dst = inst.st[dpid][srcaddr] if dst[0] != inport: log.msg('MAC has moved from '+str(dst)+'to'+str(inport), system='myswitch') else: return else: log.msg('learned MAC '+mac_to_str(packet.src)+' on %d %d'% (dpid,inport), system="myswitch") # learn or update timestamp of entry inst.st[dpid][srcaddr] = (inport, time(), packet) # Replace any old entry for (switch,mac). mac = mac_to_int(packet.src) def ip_learning(macaddr,ipaddr,dpid,inport): global inst if inst.ipmap.has_key(ipaddr): (index,macarr) = inst.ipmap[ipaddr]; isexist = False for ipentry in range(len(macarr)): if macarr[ipentry][0] == macaddr: inst.ipmap[ipaddr][1][ipentry] = (macaddr,time(),inport) print 'Update mac:%s ip:%s' % (mac_to_str(macaddr),ip_to_str(ipaddr)) isexist=True break if isexist == False: inst.ipmap[ipaddr][1].append((macaddr,time(),inport)) print 'Append mac:%s ip:%s' % (mac_to_str(macaddr),ip_to_str(ipaddr)) else: inst.ipmap[ipaddr]=(0,[(macaddr,time(),inport)]) print 'Regist mac:%s ip:%s' % (mac_to_str(macaddr),ip_to_str(ipaddr)) # -- # If we've learned the destination MAC set up a flow and # send only out of its inport. Else, flood. # -- def forward_l2_packet(dpid, inport, packet, buf, bufid): global inst print str(packet) actions = openflow.OFPP_FLOOD if isinstance(packet.next,arp): arppkt=packet.next print 'ARP: src-mac:%s src-ip:%s' % (mac_to_str(arppkt.hwsrc),ip_to_str(arppkt.protosrc)) ip_learning(arppkt.hwsrc,arppkt.protosrc,dpid,inport) elif isinstance(packet.next,ipv4): ipv4pkt=packet.next ip_learning(packet.src,ipv4pkt.srcip,dpid,inport) if isinstance(ipv4pkt.next,tcp): tcppkt=ipv4pkt.next if tcppkt.flags == tcp.SYN and inst.ipmap.has_key(ipv4pkt.dstip): (index,hosts) = inst.ipmap[ipv4pkt.dstip] inst.tcpmap[(ipv4pkt.srcip,tcppkt.srcport,ipv4pkt.dstip,tcppkt.dstport)] = (packet.dst,hosts[index % len(hosts)][0],hosts[index % len(hosts)][2],inport) actions = [[openflow.OFPAT_SET_DL_DST,hosts[index % len(hosts)][0]],[openflow.OFPAT_OUTPUT, [0,hosts[index % len(hosts)][2]]]] inst.ipmap[ipv4pkt.dstip] = (index+1,hosts) else: if inst.ipmap.has_key(ipv4pkt.dstip): if inst.tcpmap.has_key((ipv4pkt.srcip,tcppkt.srcport,ipv4pkt.dstip,tcppkt.dstport)): (orgmac,newmac,outport,srcport) = inst.tcpmap[(ipv4pkt.srcip,tcppkt.srcport,ipv4pkt.dstip,tcppkt.dstport)] actions = [[openflow.OFPAT_SET_DL_DST,newmac],[openflow.OFPAT_OUTPUT, [0,outport]]] elif inst.tcpmap.has_key((ipv4pkt.dstip,tcppkt.dstport,ipv4pkt.srcip,tcppkt.srcport)): (orgmac,newmac,outport,srcport) = inst.tcpmap[(ipv4pkt.dstip,tcppkt.dstport,ipv4pkt.srcip,tcppkt.srcport)] actions = [[openflow.OFPAT_SET_DL_SRC,orgmac],[openflow.OFPAT_OUTPUT, [0,srcport]]] inst.send_openflow(dpid, bufid, buf, actions, inport) # -- # Responsible for timing out cache entries. # Is called every 1 second. # -- def timer_callback(): global inst curtime = time() for dpid in inst.st.keys(): for entry in inst.st[dpid].keys(): if (curtime - inst.st[dpid][entry][1]) > CACHE_TIMEOUT: log.msg('timing out entry'+mac_to_str(entry)+str(inst.st[dpid][entry])+' on switch %x' % dpid, system='myswitch') inst.st[dpid].pop(entry) # ipmap # for ipaddr in inst.ipmap.keys(): # for index in range(len(inst.ipmap[ipaddr][1])-1,-1,-1): # if (curtime - inst.ipmap[ipaddr][1][index][1]) > 90: # print 'timing out entry mac:%s ip:%s' % (mac_to_str(inst.ipmap[ipaddr][1][index][0]),ip_to_str(ipaddr)) # inst.ipmap[ipaddr][1].pop(index) # if len(inst.ipmap[ipaddr][1]) == 0: # inst.ipmap.pop(ipaddr) inst.post_callback(1, timer_callback) return True def datapath_leave_callback(dpid): logger.info('Switch %x has left the network' % dpid) if inst.st.has_key(dpid): del inst.st[dpid] def datapath_join_callback(dpid, stats): logger.info('Switch %x has joined the network' % dpid) # -- # Packet entry method. # Drop LLDP packets (or we get confused) and attempt learning and # forwarding # -- def packet_in_callback(dpid, inport, reason, len, bufid, packet): if not packet.parsed: log.msg('Ignoring incomplete packet',system='myswitch') if not inst.st.has_key(dpid): log.msg('registering new switch %x' % dpid,system='myswitch') inst.st[dpid] = {} # don't forward lldp packets if packet.type == ethernet.LLDP_TYPE: return CONTINUE # learn MAC on incoming port do_l2_learning(dpid, inport, packet) forward_l2_packet(dpid, inport, packet, packet.arr, bufid) return CONTINUE class myswitch(Component): def __init__(self, ctxt): global inst Component.__init__(self, ctxt) self.st = {} self.ipmap = {} self.tcpmap = {} inst = self def install(self): inst.register_for_packet_in(packet_in_callback) inst.register_for_datapath_leave(datapath_leave_callback) inst.register_for_datapath_join(datapath_join_callback) inst.post_callback(1, timer_callback) def getInterface(self): return str(myswitch) def getFactory(): class Factory: def instance(self, ctxt): return myswitch(ctxt) return Factory()